package scanner;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by Shunjie Ding on 2018/5/27.
 */
public class UncheckedExceptionAndroidSourceScanner {
    private static Map<String, Set<String>> easyResult = new HashMap<>();
    private static final String signatureFormat;
    static {
        for (String rtException : Constants.UNCHECKED_EXCEPTIONS) {
            easyResult.put(rtException, new HashSet<>());
        }
        signatureFormat = "%s.%s: %s %s(%s)";
    }
    private static Logger LOGGER =
        LoggerFactory.getLogger(UncheckedExceptionAndroidSourceScanner.class);

    private static Set<UncheckedExceptionSourceScanner.UncheckedExceptionRelatedMethod> runScan(
        int androidVersion) {
        UncheckedExceptionSourceScanner sourceScanner = new UncheckedExceptionSourceScanner(
            new String[] {Constants.JAVA_RT_JAR, Constants.getAndroidJarPath(androidVersion)});

        return sourceScanner.scanAllInParallel(
            new String[] {Constants.getAndroidSourcePath(androidVersion)},
            Constants.UNCHECKED_EXCEPTIONS);
    }

    private static String processType(String type, Set<String> typeParams) {
        if (typeParams.contains(type)) {
            return "java.lang.Object";
        } else if (type.endsWith("[]")) {
            int nums = 0;
            while (type.endsWith("[]")) {
                type = type.substring(0, type.length() - 2);
                ++nums;
            }
            if (typeParams.contains(type)) {
                return "java.lang.Object" + StringUtils.repeat("[]", nums);
            }
        }
        return type.replaceAll("<.+>", "");
    }

    private static String getSignature(
        UncheckedExceptionSourceScanner.UncheckedExceptionRelatedMethod method) {
        Set<String> typeParams = new HashSet<>(Arrays.asList(method.getTypeParameters()));
        method.setReturnTypeName(processType(method.getReturnTypeName(), typeParams));

        String[] paramTypes = method.getParameterTypeName();
        for (int i = 0; i < paramTypes.length; ++i) {
            paramTypes[i] = processType(paramTypes[i], typeParams);
        }
        method.setParameterTypeName(paramTypes);

        StringBuilder parameters = new StringBuilder();
        for (String parameter : method.getParameterTypeName()) {
            parameters.append(parameter).append(",");
        }
        if (parameters.length() != 0) {
            parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1));
        }
        return String.format(signatureFormat, method.getPackageName(), method.getClassName(),
            method.getReturnTypeName(), method.getMethodName(), parameters.toString());
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            LOGGER.info("please provider android source version");
            return;
        }

        int androidVersion = Integer.parseInt(args[0]);
        Set<UncheckedExceptionSourceScanner.UncheckedExceptionRelatedMethod> relatedMethods =
            runScan(androidVersion);

        relatedMethods.size();

        for (UncheckedExceptionSourceScanner.UncheckedExceptionRelatedMethod method :
            relatedMethods) {
            String signature = getSignature(method);
            if (method.getUncheckedExceptionNames() != null
                && method.getUncheckedExceptionNames().length > 0) {
                for (String aException : method.getUncheckedExceptionNames()) {
                    for (String WN : Constants.UNCHECKED_EXCEPTIONS) {
                        if (WN.contains(aException)) {
                            easyResult.get(WN).add("*throw %% " + signature);
                        }
                    }
                }
            }
            if (method.getExceptionsFromJavaDoc() != null
                && method.getExceptionsFromJavaDoc().length > 0) {
                for (UncheckedExceptionSourceScanner.UncheckedExceptionRelatedMethod
                         .JavadocType singleTag : method.getExceptionsFromJavaDoc()) {
                    for (String WN : Constants.UNCHECKED_EXCEPTIONS) {
                        if (WN.contains(singleTag.getType())) {
                            easyResult.get(WN).add(singleTag.getTag() + " %% " + signature);
                        }
                    }
                }
            }
        }
        try {
            Workbook wb = new HSSFWorkbook();
            Sheet sheet = wb.createSheet();
            int max = 0;
            for (String key : easyResult.keySet()) {
                int length = easyResult.get(key).size();
                if (length > max)
                    max = length;
            }
            for (int row = 0; row < max + 3; row++) {
                sheet.createRow(row);
            }
            int col = 0;
            for (String key : easyResult.keySet()) {
                sheet.getRow(0).createCell(col).setCellValue(key);
                int row = 1;
                for (String signature : easyResult.get(key)) {
                    sheet.getRow(row++).createCell(col).setCellValue(signature);
                }
                col++;
            }
            wb.write(new FileOutputStream(new File("related-unchecked-exceptions.xls")));
            wb.close();
        } catch (EncryptedDocumentException | IOException e) {
            e.printStackTrace();
        }
    }
}
